package com.whfc.influx.dao.impl;

import com.whfc.common.enums.DelFlag;
import com.whfc.common.obd.ObdDataUtil;
import com.whfc.common.obd.ObdFrame;
import com.whfc.common.result.PageData;
import com.whfc.common.util.JSONUtil;
import com.whfc.influx.dao.AppDeviceObdLogDao;
import com.whfc.mach.constant.ObdMeasurement;
import com.whfc.mach.dto.obd.*;
import com.whfc.mach.entity.AppDeviceObdLog;
import org.apache.commons.lang3.StringUtils;
import org.influxdb.InfluxDB;
import org.influxdb.dto.BatchPoints;
import org.influxdb.dto.Point;
import org.influxdb.dto.Query;
import org.influxdb.dto.QueryResult;
import org.influxdb.impl.InfluxDBMapper;
import org.influxdb.querybuilder.Select;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;

import static org.influxdb.querybuilder.BuiltQuery.QueryBuilder.*;

/**
 * @Description:
 * @author: xugcheng
 * @version: 1.0
 * @date: 2020-11-03 16:58
 */
@Service
public class AppDeviceObdLogDaoImpl implements AppDeviceObdLogDao {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 数据库名称
     */
    private static final String database = ObdMeasurement.DATABASE;

    /**
     * 表名
     */
    private static final String measurement = ObdMeasurement.MEASUREMENT;

    /**
     * 保留策略:保存10年数据
     */
    private static final String retentionPolicy = ObdMeasurement.RETENTION_POLICY;

    /**
     * 帧数据field模板
     */
    public static final String frame_data_pattern = "frameData%s";

    /**
     * 时间单位:秒
     */
    private static final TimeUnit timeUnit = TimeUnit.SECONDS;

    @Autowired
    private InfluxDB influxDB;

    @Autowired
    private InfluxDBMapper influxDBMapper;

    @Override
    public void insert(AppDeviceObdLog log) {
        logger.debug("influxdb的insert方法,frameId:{}", log.getFrameIdHex());
        influxDB.setDatabase(database);
        influxDB.setRetentionPolicy(retentionPolicy);
        influxDB.write(this.buildDataPoint(log));
    }

    @Override
    public void batchInsert(List<AppDeviceObdLog> logList) {
        logger.debug("influxdb的batchInsert方法");
        BatchPoints.Builder batchBuiler = BatchPoints.builder();
        for (AppDeviceObdLog record : logList) {
            Point point = this.buildDataPoint(record);
            batchBuiler.point(point);
        }
        influxDB.setDatabase(database);
        influxDB.setRetentionPolicy(retentionPolicy);
        influxDB.write(batchBuiler.build());
    }

    @Override
    public void insert(AppDeviceObdLog log, List<ObdFrame> frameList) {
        logger.debug("influxdb的Insert方法");
        influxDB.setDatabase(database);
        influxDB.setRetentionPolicy(retentionPolicy);
        influxDB.write(this.buildDataPoint(log, frameList));
    }

    @Override
    public PageData<AppDeviceObdLogDTO> selectObdLogByDeviceId(Integer deviceId, Date startTime, Date endTime, Integer pageNum, Integer pageSize, String[] columns) {
        logger.debug("influxdb的selectMachDataLogListByDeviceId方法");

        //统计总数量
        Query countQuery = select().count("status")
                .from(database, measurement)
                .where(eq("deviceId", String.valueOf(deviceId)))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()));
        logger.debug(countQuery.getCommand());
        QueryResult queryResult = influxDB.query(countQuery, TimeUnit.MILLISECONDS);
        Map<String, Object> resultMap = this.parseQueryResultMap(queryResult);
        int total = Double.valueOf(resultMap.getOrDefault("count", 0).toString()).intValue();
        logger.debug("数量总数量total={}", total);

        //分页查询
        int pages = (total / pageSize) + ((total % pageSize) == 0 ? 0 : 1);
        int offset = (pageNum - 1) * pageSize;
        int limit = pageSize;
        List<AppDeviceObdLogDTO> list = Collections.EMPTY_LIST;
        if (pageNum <= pages) {
            Select select = select(columns).from(database, measurement)
                    .where(eq("deviceId", String.valueOf(deviceId)))
                    .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                    .andNested()
                    .and(gte("time", startTime.toInstant().toString()))
                    .and(lte("time", endTime.toInstant().toString()))
                    .close()
                    .orderBy(desc());
            Query query = offset == 0 ? select.limit(limit) : select.limit(limit, offset);
            logger.debug(query.getCommand());
            list = influxDBMapper.query(query, AppDeviceObdLogDTO.class);
            this.transformTime(list);
        }

        PageData<AppDeviceObdLogDTO> pageData = new PageData<>();
        pageData.setList(list);
        pageData.setTotal((long) total);
        pageData.setPages(pages);
        pageData.setPageNum(pageNum);
        pageData.setPageSize(pageSize);
        return pageData;
    }

    @Override
    public AppDeviceObdLogDTO selectObdLogByDeviceId(Integer deviceId, Date time) {
        logger.debug("influxdb的selectObdLogByDeviceId方法");
        String[] columns = new String[]{"time", "status", "lng", "lat", "location"};
        Query query = select().from(database, measurement)
                .where(eq("deviceId", String.valueOf(deviceId)))
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .andNested()
                .and(eq("time", time.toInstant().toString()))
                .close()
                .orderBy(asc());
        logger.debug(query.getCommand());
        List<AppDeviceObdLogDTO> list = influxDBMapper.query(query, AppDeviceObdLogDTO.class);
        this.transformTime(list);
        if (list.size() > 0) {
            return list.get(0);
        }
        return null;
    }

    @Override
    public List<AppDeviceObdLogDTO> selectObdLogByMachId(Integer machId, Date startTime, Date endTime) {
        String[] columns = new String[]
                {"time", "status", "lng", "lat", "location", "workState", "netState", "totalLength1", "totalLength2", "totalOilWear", "batteryPower", "speed1", "speed2", "force", "tension1", "tension2"};
        return this.selectObdLogByMachId(machId, startTime, endTime, columns);
    }

    @Override
    public List<AppDeviceObdLogDTO> selectObdLogByMachId(Integer machId, Date startTime, Date endTime, String[] columns) {
        logger.debug("influxdb的selectObdLogByMachId方法");
        Query query = select(columns).from(database, measurement)
                .where(eq("machId", String.valueOf(machId)))
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .orderBy(asc());
        logger.debug(query.getCommand());
        List<AppDeviceObdLogDTO> list = influxDBMapper.query(query, AppDeviceObdLogDTO.class);
        this.transformTime(list);
        return list;
    }

    @Override
    public ObdOilDTO selectObdOil(Integer machId, Date startTime, Date endTime) {
        StringBuilder raw = new StringBuilder();
        raw.append("first(totalOilWear) as firstTotalOilWear,");
        raw.append("last(totalOilWear) as lastTotalOilWear");
        Query countQuery = select().raw(raw.toString())
                .from(database, measurement)
                .where(eq("machId", String.valueOf(machId)))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .orderBy(asc());
        logger.debug(countQuery.getCommand());
        QueryResult queryResult = influxDB.query(countQuery, TimeUnit.MILLISECONDS);
        Map<String, Object> resultMap = this.parseQueryResultMap(queryResult);
        String json = JSONUtil.toString(resultMap);
        logger.debug("查询结果:{}", json);
        return JSONUtil.parseObject(json, ObdOilDTO.class);
    }

    @Override
    public ObdStateDTO selectObdState(Integer machId, Date startTime, Date endTime) {
        StringBuilder raw = new StringBuilder();
        raw.append("last(engineSpeed) as engineSpeed,");
        raw.append("last(speed1) as speed1,");
        raw.append("last(speed2) as speed2,");
        raw.append("last(speed3) as speed3,");
        raw.append("last(speed4) as speed4");
        Query countQuery = select().raw(raw.toString())
                .from(database, measurement)
                .where(eq("machId", String.valueOf(machId)))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .orderBy(asc());
        logger.debug(countQuery.getCommand());
        QueryResult queryResult = influxDB.query(countQuery, TimeUnit.MILLISECONDS);
        Map<String, Object> resultMap = this.parseQueryResultMap(queryResult);
        String json = JSONUtil.toString(resultMap);
        logger.debug("查询结果:{}", json);
        return JSONUtil.parseObject(json, ObdStateDTO.class);
    }

    @Override
    public ObdLengthDTO selectObdLength(Integer machId, Date startTime, Date endTime, String column) {
        StringBuilder raw = new StringBuilder();
        raw.append(String.format("first(%s) as firstTotalLength,", column));
        raw.append(String.format("last(%s) as lastTotalLength", column));
        Query countQuery = select().raw(raw.toString())
                .from(database, measurement)
                .where(eq("machId", String.valueOf(machId)))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .orderBy(asc());
        logger.debug(countQuery.getCommand());
        QueryResult queryResult = influxDB.query(countQuery, TimeUnit.MILLISECONDS);
        Map<String, Object> resultMap = this.parseQueryResultMap(queryResult);
        String json = JSONUtil.toString(resultMap);
        logger.debug("查询结果:{}", json);
        return JSONUtil.parseObject(json, ObdLengthDTO.class);
    }

    @Override
    public List<ObdDataDTO> selectObdOilLogByMachId(Integer machId, Date startTime, Date endTime) {
        StringBuilder raw = new StringBuilder();
        raw.append("totalOilWear as oilWear,");
        raw.append("time");
        Query countQuery = select().raw(raw.toString())
                .from(database, measurement)
                .where(eq("machId", String.valueOf(machId)))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .and(gte("totalOilWear", 0))
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .orderBy(asc());
        logger.debug(countQuery.getCommand());
        List<ObdDataDTO> list = influxDBMapper.query(countQuery, ObdDataDTO.class);
        for (ObdDataDTO obdDataDTO : list) {
            obdDataDTO.setTime(Date.from(obdDataDTO.getDeviceTime()));
        }
        return list;

    }

    @Override
    public List<ObdDataDTO> selectObdMileLogByMachId(Integer machId, Date startTime, Date endTime) {
        StringBuilder raw = new StringBuilder();
        raw.append("totalLength1 as mile1,");
        raw.append("totalLength2 as mile2,");
        raw.append("time");
        Query countQuery = select().raw(raw.toString())
                .from(database, measurement)
                .where(eq("machId", String.valueOf(machId)))
                .andNested()
                .and(gte("time", startTime.toInstant().toString()))
                .and(lte("time", endTime.toInstant().toString()))
                .close()
                .andNested()
                .or(gte("totalLength1", 0))
                .or(gte("totalLength2", 0))
                .close()
                .and(eq("delFlag", DelFlag.UNDELETE.getValue()))
                .orderBy(asc());
        logger.debug(countQuery.getCommand());
        List<ObdDataDTO> list = influxDBMapper.query(countQuery, ObdDataDTO.class);
        for (ObdDataDTO obdDataDTO : list) {
            obdDataDTO.setTime(Date.from(obdDataDTO.getDeviceTime()));
        }
        return list;
    }

    /**
     * 构建数据点
     *
     * @param record
     * @return
     */
    private Point buildDataPoint(AppDeviceObdLog record) {
        logger.debug("influxdb的buildDataPoint方法");
        Point.Builder builder = Point
                .measurement(measurement)
                .time(record.getTime().toInstant().getEpochSecond(), timeUnit);
        if (!ObjectUtils.isEmpty(record.getDeviceId())) {
            builder.tag("deviceId", String.valueOf(record.getDeviceId()));
        }
        if (!ObjectUtils.isEmpty(record.getMachId())) {
            builder.tag("machId", String.valueOf(record.getMachId()));
        }

        if (!ObjectUtils.isEmpty(record.getStatus())) {
            builder.addField("status", record.getStatus());
        }
        if (!ObjectUtils.isEmpty(record.getWorkState())) {
            builder.addField("workState", record.getWorkState());
        }
        if (!ObjectUtils.isEmpty(record.getNetState())) {
            builder.addField("netState", record.getNetState());
        }
        if (!ObjectUtils.isEmpty(record.getTotalOilWear())) {
            builder.addField("totalOilWear", record.getTotalOilWear());
        }
        if (!ObjectUtils.isEmpty(record.getEngineSpeed())) {
            builder.addField("engineSpeed", record.getEngineSpeed());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed1())) {
            builder.addField("speed1", record.getSpeed1());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed2())) {
            builder.addField("speed2", record.getSpeed2());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed3())) {
            builder.addField("speed3", record.getSpeed3());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed4())) {
            builder.addField("speed4", record.getSpeed4());
        }
        if (!ObjectUtils.isEmpty(record.getTotalLength1())) {
            builder.addField("totalLength1", record.getTotalLength1());
        }
        if (!ObjectUtils.isEmpty(record.getTotalLength2())) {
            builder.addField("totalLength2", record.getTotalLength2());
        }
        if (!ObjectUtils.isEmpty(record.getBatteryPower())) {
            builder.addField("batteryPower", record.getBatteryPower());
        }
        if (!ObjectUtils.isEmpty(record.getBatteryState())) {
            builder.addField("batteryState", record.getBatteryState());
        }
        if (!ObjectUtils.isEmpty(record.getMasterVer())) {
            builder.addField("masterVer", record.getMasterVer());
        }
        if (!ObjectUtils.isEmpty(record.getLng())) {
            builder.addField("lng", record.getLng());
        }
        if (!ObjectUtils.isEmpty(record.getLat())) {
            builder.addField("lat", record.getLat());
        }
        if (!ObjectUtils.isEmpty(record.getLngWgs84())) {
            builder.addField("lngWgs84", record.getLngWgs84());
        }
        if (!ObjectUtils.isEmpty(record.getLatWgs84())) {
            builder.addField("latWgs84", record.getLatWgs84());
        }
        if (!StringUtils.isEmpty(record.getLocation())) {
            builder.addField("location", record.getLocation());
        }
        if (!ObjectUtils.isEmpty(record.getForce())) {
            builder.addField("force", record.getForce());
        }
        if (!ObjectUtils.isEmpty(record.getTension1())) {
            builder.addField("tension1", record.getTension1());
        }
        if (!ObjectUtils.isEmpty(record.getTension2())) {
            builder.addField("tension2", record.getTension2());
        }

        if (!ObjectUtils.isEmpty(record.getFrameId()) && ObdDataUtil.isValidFrameId(record.getFrameId())
                && !ObjectUtils.isEmpty(record.getFrameDlc()) && record.getFrameDlc() > 0) {
            String field = String.format(frame_data_pattern, StringUtils.upperCase(record.getFrameIdHex()));
            builder.addField(field, record.getFrameData());
        }
        if (!ObjectUtils.isEmpty(record.getDelFlag())) {
            builder.addField("delFlag", record.getDelFlag());
        }
        builder.addField("serverTime", System.currentTimeMillis() / 1000);
        return builder.build();
    }

    /**
     * 构建数据点
     *
     * @param record
     * @param frameList
     * @return
     */
    private Point  buildDataPoint(AppDeviceObdLog record, List<ObdFrame> frameList) {
        logger.debug("influxdb的buildDataPoint方法");
        Point.Builder builder = Point
                .measurement(measurement)
                .time(record.getTime().toInstant().getEpochSecond(), timeUnit);
        if (!ObjectUtils.isEmpty(record.getDeviceId())) {
            builder.tag("deviceId", String.valueOf(record.getDeviceId()));
        }
        if (!ObjectUtils.isEmpty(record.getMachId())) {
            builder.tag("machId", String.valueOf(record.getMachId()));
        }
        if (!ObjectUtils.isEmpty(record.getStatus())) {
            builder.addField("status", record.getStatus());
        }
        if (!ObjectUtils.isEmpty(record.getWorkState())) {
            builder.addField("workState", record.getWorkState());
        }
        if (!ObjectUtils.isEmpty(record.getNetState())) {
            builder.addField("netState", record.getNetState());
        }
        if (!ObjectUtils.isEmpty(record.getTotalOilWear())) {
            builder.addField("totalOilWear", record.getTotalOilWear());
        }
        if (!ObjectUtils.isEmpty(record.getEngineSpeed())) {
            builder.addField("engineSpeed", record.getEngineSpeed());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed1())) {
            builder.addField("speed1", record.getSpeed1());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed2())) {
            builder.addField("speed2", record.getSpeed2());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed3())) {
            builder.addField("speed3", record.getSpeed3());
        }
        if (!ObjectUtils.isEmpty(record.getSpeed4())) {
            builder.addField("speed4", record.getSpeed4());
        }
        if (!ObjectUtils.isEmpty(record.getTotalLength1())) {
            builder.addField("totalLength1", record.getTotalLength1());
        }
        if (!ObjectUtils.isEmpty(record.getTotalLength2())) {
            builder.addField("totalLength2", record.getTotalLength2());
        }
        if (!ObjectUtils.isEmpty(record.getBatteryPower())) {
            builder.addField("batteryPower", record.getBatteryPower());
        }
        if (!ObjectUtils.isEmpty(record.getBatteryState())) {
            builder.addField("batteryState", record.getBatteryState());
        }
        if (!ObjectUtils.isEmpty(record.getMasterVer())) {
            builder.addField("masterVer", record.getMasterVer());
        }
        if (!ObjectUtils.isEmpty(record.getLng())) {
            builder.addField("lng", record.getLng());
        }
        if (!ObjectUtils.isEmpty(record.getLat())) {
            builder.addField("lat", record.getLat());
        }
        if (!ObjectUtils.isEmpty(record.getLngWgs84())) {
            builder.addField("lngWgs84", record.getLngWgs84());
        }
        if (!ObjectUtils.isEmpty(record.getLatWgs84())) {
            builder.addField("latWgs84", record.getLatWgs84());
        }
        if (!StringUtils.isEmpty(record.getLocation())) {
            builder.addField("location", record.getLocation());
        }
        if (!ObjectUtils.isEmpty(record.getForce())) {
            builder.addField("force", record.getForce());
        }
        if (!ObjectUtils.isEmpty(record.getTension1())) {
            builder.addField("tension1", record.getTension1());
        }
        if (!ObjectUtils.isEmpty(record.getTension2())) {
            builder.addField("tension2", record.getTension2());
        }

        if (frameList != null && !frameList.isEmpty()) {
            for (ObdFrame frame : frameList) {
                String frameIdHex = frame.getFrameIdHex();
                String frameData = frame.getFrameData();
                String field = String.format(frame_data_pattern, StringUtils.upperCase(frameIdHex));
                builder.addField(field, frameData);
            }
        }
        if (!ObjectUtils.isEmpty(record.getDelFlag())) {
            builder.addField("delFlag", record.getDelFlag());
        }
        builder.addField("serverTime", System.currentTimeMillis() / 1000);
        return builder.build();
    }

    /**
     * 时间类型转换
     *
     * @param list
     */
    private void transformTime(List<AppDeviceObdLogDTO> list) {
        for (AppDeviceObdLogDTO dto : list) {
            dto.setTime(Date.from(dto.getDeviceTime()));
            if (!ObjectUtils.isEmpty(dto.getServerTime())) {
                dto.setCreateTime(new Date(dto.getServerTime() * 1000));
            }
        }
    }

    /**
     * 解析查询结果
     *
     * @param queryResult
     * @return
     */
    private Map<String, Object> parseQueryResultMap(QueryResult queryResult) {
        Map<String, Object> map = null;
        if (queryResult != null) {
            List<QueryResult.Result> results = queryResult.getResults();
            if (results != null) {
                QueryResult.Result result = results.get(0);
                List<QueryResult.Series> seriesList = result.getSeries();
                if (seriesList != null && seriesList.size() > 0) {
                    List<String> columns = seriesList.get(0).getColumns();
                    List<List<Object>> values = seriesList.get(0).getValues();
                    logger.debug("columns:" + columns);
                    logger.debug("values:" + values);
                    if (values != null && values.size() > 0) {
                        List<Object> valueArr = values.get(0);
                        map = new HashMap<>(columns.size());
                        for (int i = 0; i < columns.size(); i++) {
                            String column = columns.get(i);
                            Object value = valueArr.get(i);
                            map.put(column, value);
                        }
                    }

                }
            }
        }
        if (map == null) {
            map = Collections.EMPTY_MAP;
        }
        return map;
    }
}
